﻿using Common;
using DB.MongoDB.Model;
using Log4_Net;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DB.MongoDB
{
    public class MongoDbHelper<TEntity> : IMongoDbHelper<TEntity>
      where TEntity : IEntity<ObjectId>
    {
        private ILogger _logger = null;
        private IMongoDatabase db = null;

        IMongoCollection<TEntity> collection;

        public MongoDbHelper(ILogHelper _ILogHelper)
        {
            _logger = _ILogHelper.GetLogger();
            this.db = MongoDB.GetDb();
            collection = db.GetCollection<TEntity>(typeof(TEntity).Name);

        }

        public async Task<TEntity> Insert(TEntity entity)
        {
            try
            {
                var flag = ObjectId.GenerateNewId();
                entity.GetType().GetProperty("Id").SetValue(entity, flag);
                await collection.InsertOneAsync(entity);
                return entity;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message + ex.StackTrace);
                return default(TEntity);
            }
        }
        public async Task<TEntity[]> Insert(TEntity[] entity)
        {
            try
            {
                foreach (var i in entity)
                {
                    var flag = ObjectId.GenerateNewId();
                    i.GetType().GetProperty("Id").SetValue(entity, flag);
                }
                await collection.InsertManyAsync(entity);
                return entity;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message + ex.StackTrace);
                return default(TEntity[]);
            }
        }
        public async Task<bool> Modify(string id, string field, string value)
        {
            try
            {
                var filter = Builders<TEntity>.Filter.Eq("Id", ObjectId.Parse(id));
                var updated = Builders<TEntity>.Update.Set(field, value);
                await collection.UpdateOneAsync(filter, updated);

                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message + ex.StackTrace);
                return false;
            }
        }

        public async Task<bool> Update(TEntity entity)
        {
            try
            {
                var old = collection.Find(e => e.Id.Equals(entity.Id)).ToList().FirstOrDefault();

                foreach (var prop in entity.GetType().GetProperties())
                {
                    var newValue = prop.GetValue(entity);
                    var oldValue = old.GetType().GetProperty(prop.Name).GetValue(old);
                    if (newValue != null)
                    {
                        if (!newValue.Equals(oldValue))
                        {
                            old.GetType().GetProperty(prop.Name).SetValue(old, newValue);
                        }
                    }
                }

                var filter = Builders<TEntity>.Filter.Eq("Id", entity.Id);
                await collection.ReplaceOneAsync(filter, old);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message + ex.StackTrace);
                return false;
            }
        }
        public async Task<bool> UpdateManyAsync(FilterGroup filterGroup, Dictionary<string, string> setfield)
        {
            try
            {
                var filters = GetFilter(filterGroup);
                UpdateDefinition<TEntity> update = null;
                foreach (var i in setfield)
                {
                    if (update == null)
                        update = Builders<TEntity>.Update.Set(i.Key, i.Value);
                    else
                    {
                        update = update.Set(i.Key, i.Value);
                    }
                }
                await collection.UpdateManyAsync(filters, update);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message + ex.StackTrace);
                return false;
            }
        }
        public async Task<bool> Delete(string id)
        {
            try
            {
                var filter = Builders<TEntity>.Filter.Eq("Id", id.ToObjectId());
                await collection.DeleteOneAsync(filter);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message + ex.StackTrace);
                return false;
            }
        }
        public async Task<bool> Delete(TEntity entity)
        {
            try
            {
                var filter = Builders<TEntity>.Filter.Eq("Id", entity.Id);
                await collection.DeleteOneAsync(filter);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message + ex.StackTrace);
                return false;
            }
        }
        public async Task<bool> Delete(FilterGroup filterGroup)
        {
            try
            {
                var filters = GetFilter(filterGroup);
                await collection.DeleteManyAsync(filters);
                return true;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message + ex.StackTrace);
                return false;
            }
        }
        public async Task<TEntity> QueryOne(string id)
        {
            try
            {
                return await collection.Find(a => a.Id == ObjectId.Parse(id)).FirstOrDefaultAsync();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message + ex.StackTrace);
                return default(TEntity);
            }
        }
        public async Task<TEntity> QueryOneFilter(FilterGroup filterGroup)
        {
            try
            {
                var filters = GetFilter(filterGroup);
                return await collection.Find(filters).FirstOrDefaultAsync();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message + ex.StackTrace);
                return default(TEntity);
            }
        }
        public async Task<List<TEntity>> QueryFilter(FilterGroup filterGroup)
        {
            try
            {
                var filters = GetFilter(filterGroup);
                return await collection.Find(filters).ToListAsync();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message + ex.StackTrace);
                return null;
            }
        }
        public IMongoQueryable<TEntity> AsQueryable()
        {
            try
            {
                return collection.AsQueryable();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message + ex.StackTrace);
                return null;
            }
        }

       
        public void GetFilterWhile(List<FilterGroup> filterGroup, ref FilterDefinition<TEntity> filter)
        {

            foreach (var i in filterGroup)
            {
                GetFilters(i, ref filter);
                GetFilterWhile(i.FilterGroups, ref filter);
            }

        }

        public FilterDefinition<TEntity> GetFilter(FilterGroup filterGroup)
        {

            FilterDefinition<TEntity> filter2 = null;
            GetFilterWhile(filterGroup.FilterGroups, ref filter2);
            GetFilters(filterGroup, ref filter2);
            return filter2;
        }

        public void GetFilters(FilterGroup filterGroup, ref FilterDefinition<TEntity> filter2)
        {
            var filters2 = new List<FilterDefinition<TEntity>>();
            foreach (var j in filterGroup.FilterRules)
            {
                FilterDefinition<TEntity> f = null;
                switch (j.CompareType)
                {
                    case CompareType.equal:
                        f = Builders<TEntity>.Filter.Eq(j.Field, j.Value);
                        break;
                    case CompareType.notEqual:
                        f = Builders<TEntity>.Filter.Ne(j.Field, j.Value);
                        break;
                    case CompareType.bigger:
                        f = Builders<TEntity>.Filter.Gt(j.Field, j.Value);
                        break;
                    case CompareType.biggerEqual:
                        f = Builders<TEntity>.Filter.Gte(j.Field, j.Value);
                        break;
                    case CompareType.less:
                        f = Builders<TEntity>.Filter.Lt(j.Field, j.Value);
                        break;
                    case CompareType.lessEqual:
                        f = Builders<TEntity>.Filter.Lte(j.Field, j.Value);
                        break;
                    case CompareType.contain:
                        f = Builders<TEntity>.Filter.In(j.Field, j.Value);
                        break;
                    case CompareType.notContain:
                        f = Builders<TEntity>.Filter.Nin(j.Field, j.Value);
                        break;
                    default:
                        f = Builders<TEntity>.Filter.Eq(j.Field, j.Value);
                        break;
                }

                filters2.Add(f);
            }
            if (filter2 != null)
                filters2.Add(filter2);
            switch (filterGroup.FilterType)
            {
                case FilterType.and:
                    filter2 = Builders<TEntity>.Filter.And(filters2);
                    break;
                case FilterType.or:
                    filter2 = Builders<TEntity>.Filter.Or(filters2);
                    break;
                default:
                    filter2 = Builders<TEntity>.Filter.And(filters2);
                    break;
            }
        }
    }
}
